package org.litu.app.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.litu.app.dao.SysFilesMapper;
import org.litu.app.entity.system.SysFiles;
import org.litu.app.service.ISysFilesService;
import org.litu.app.util.AppFileUtil;
import org.litu.base.service.impl.BaseServiceImpl;
import org.litu.core.login.UserInfo;
import org.litu.util.file.FileUtil;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 文件管理表 服务实现类
 * </p>
 *
 * @author ltgk
 * @since 2019-12-09
 */
@Service
public class SysFilesServiceImpl extends BaseServiceImpl<SysFilesMapper, SysFiles> implements ISysFilesService {

    /************************* 上传文件 *****************************/

    /**
     * 上传文件
     *
     * @param user    当前用户
     * @param dirName 结尾以反斜线结束
     * @param file    文件信息
     * @return
     */
    @Override
    public SysFiles uploadFile(UserInfo user, String dirName, MultipartFile file) {
        // 文件名处理
        String originalName = file.getOriginalFilename();
        try {
            String fileType = FileUtil.getFileSuffix(originalName);
            // 相对路径
            String relativePath = "";
            if (StringUtils.isNotBlank(dirName)) {
                relativePath = dirName + AppFileUtil.newFileName(fileType);
            } else {
                relativePath = AppFileUtil.getSavePath(fileType);
            }
            // 绝对路径
            String realPath = AppFileUtil.getRealPath(relativePath);

            // 实际文件保存
            File fileUpload = new File(realPath);
            FileUtil.makeDirectory(fileUpload);
            file.transferTo(fileUpload);

            // 缩略图处理
            AppFileUtil.generateThumbnail(realPath, fileType, relativePath);

            // 文件信息存储到数据库
            SysFiles sysFiles = new SysFiles();
            sysFiles.setOriginName(originalName);
            sysFiles.setLocation(relativePath);
            sysFiles.setFileType(fileType);
            sysFiles.setFileSize(file.getSize());
            sysFiles.setCreateBy(user.getId());
            sysFiles.setCreateTime(new Date());
            save(sysFiles);
            return sysFiles;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 其他接口接收文件的保存接口
     *
     * @param file 保存到本地的文件
     * @return 添加成功返回保存后的文件实体信息
     */
    @Override
    public SysFiles save(UserInfo user, File file) {
        if (!file.exists()) {
            return null;
        }

        String originalName = file.getName();
        String fileType = FileUtil.getFileSuffix(originalName);
        String relativePath = AppFileUtil.getSavePath(fileType);
        String realPath = AppFileUtil.getRealPath(relativePath);

        // 实际文件保存
        File fileUpload = new File(realPath);
        FileUtil.makeDirectory(fileUpload);
        file.renameTo(fileUpload);
        // 缩略图处理
        AppFileUtil.generateThumbnail(realPath, fileType, relativePath);
        // 文件信息存储到数据库
        SysFiles sysFiles = new SysFiles();
        sysFiles.setOriginName(originalName);
        sysFiles.setLocation(relativePath);
        sysFiles.setFileType(fileType);
        sysFiles.setFileSize(file.length());
        sysFiles.setCreateBy(user.getId());
        sysFiles.setCreateTime(new Date());
        save(sysFiles);
        return sysFiles;
    }

    /************************* 获取文件 *****************************/

    /**
     * 加载获取文件
     *
     * @param filePath
     * @param response
     */
    @Override
    public void load(String filePath, HttpServletResponse response) {
        String realPath = AppFileUtil.getRealPath(filePath);
        try (FileInputStream in = new FileInputStream(realPath); ServletOutputStream output = response.getOutputStream()) {
            response.setContentLength(in.available());
            response.setContentType(Files.probeContentType(Paths.get(realPath)));
            IOUtils.copy(in, output);
            output.flush();
            in.close();
            output.close();
        } catch (FileNotFoundException e) {
            fileErrorReturn(response, "未找到资源");
        } catch (IOException e) {
            fileErrorReturn(response, "文件读取错误");
        }
    }

    /**
     * 分段加载
     *
     * @param filePath
     * @param request
     * @param response
     */
    @Override
    public void loadRange(String filePath, HttpServletRequest request, HttpServletResponse response) {
        String realPath = AppFileUtil.getRealPath(filePath);

        // 计算range的开始和结束数值
        String range = request.getHeader("Range");
        // 提取开始和结束的字节数, 以及本次需要返回的字节数
        long start = 0, end = 0, requestSize = Integer.MAX_VALUE;
        if (StringUtils.isNotBlank(range)) {
            if (range.startsWith("bytes=")) {
                String[] ranges = range.split("=");
                if (ranges.length > 1) {
                    String[] rangeDatas = ranges[1].split("-");
                    start = Integer.parseInt(rangeDatas[0]);
                    if (rangeDatas.length > 1) {
                        end = Integer.parseInt(rangeDatas[1]);
                    }
                }
            }
            if (end != 0 && end > start) {
                requestSize = end - start + 1;
            }
        }

        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Last-Modified", new Date().toString());
        response.addHeader("Access-Control-Expose-Headers", "Accept-Ranges,Content-Range");
        try (RandomAccessFile randomFile = new RandomAccessFile(realPath, "r"); ServletOutputStream output = response.getOutputStream()) {
            long contentLength = randomFile.length();
            //第一次请求只返回content length来让客户端请求多次实际数据
            if (range == null) {
                response.setContentLength((int) contentLength);
            } else {
                //以后的多次以断点续传的方式来返回文件数据
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

                // 计算返回头中长度和range的信息
                long length = 0;
                if (end > 0) {
                    length = end - start + 1;
                    response.setContentLength((int) length);
                    response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + contentLength);
                } else {
                    length = contentLength - start;
                    response.setContentLength((int) length);
                    response.setHeader("Content-Range", "bytes " + start + "-" + (contentLength - 1) + "/" + contentLength);
                }
            }

            // 返回文件流数据
            long needSize = requestSize;
            byte[] buffer = new byte[4096];
            randomFile.seek(start);
            while (needSize > 0) {
                int len = randomFile.read(buffer);

                if (needSize < buffer.length) {
                    output.write(buffer, 0, (int) needSize);
                } else {
                    output.write(buffer, 0, len);
                    if (len < buffer.length) {
                        break;
                    }
                }
                needSize -= buffer.length;
            }
        } catch (FileNotFoundException e) {
            fileErrorReturn(response, "未找到资源");
        } catch (IOException e) {
            fileErrorReturn(response, "文件读取错误");
        }
    }

    /**
     * 获取缩略图
     *
     * @param fileType
     * @param filePath
     * @param response
     */
    @Override
    public void loadThumbnail(String fileType, String filePath, HttpServletResponse response) {
        // 检测缩略图是否存在，如果不存在，则创建该缩略图
        File thumbnailFile = new File(AppFileUtil.getThumbnailRealPath(filePath));
        if (!thumbnailFile.exists()) {
            AppFileUtil.generateThumbnail(AppFileUtil.getRealPath(filePath), fileType, filePath);
        }

        try (FileInputStream in = new FileInputStream(thumbnailFile); ServletOutputStream output = response.getOutputStream()) {
            response.setContentLength(in.available());
            response.setContentType(Files.probeContentType(thumbnailFile.toPath()));
            IOUtils.copy(in, output);
            output.flush();
        } catch (FileNotFoundException e) {
            fileErrorReturn(response, "未找到资源");
        } catch (IOException e) {
            fileErrorReturn(response, "文件读取错误");
        }
    }

    /**
     * 下载文件
     *
     * @param fileName
     * @param response
     */
    @Override
    public void download(String filePath, String fileName, HttpServletResponse response) {
        // 绝对路径
        String realPath = AppFileUtil.getRealPath(filePath);
        try (FileInputStream in = new FileInputStream(realPath); ServletOutputStream output = response.getOutputStream()) {
            response.setContentType("application/octet-stream;charset=UTF-8");
            response.setHeader("Content-disposition", "attachment; filename=" + new String(fileName.getBytes(), StandardCharsets.ISO_8859_1));
            long filelen = in.getChannel().size();
            response.setContentLengthLong(filelen);
            IOUtils.copy(in, output);
            if (output != null) {
                output.flush();
                output.close();
            }
        } catch (FileNotFoundException e) {
            fileErrorReturn(response, "未找到资源");
        } catch (IOException e) {
            fileErrorReturn(response, "文件读取错误");
        }
    }

    /**
     * 文件读取错误时返回的信息
     *
     * @param response
     * @param errorMsg
     */
    private void fileErrorReturn(HttpServletResponse response, String errorMsg) {
        PrintWriter out = null;
        try {
            response.setContentType("text/plain;charset=UTF-8");
            response.setStatus(HttpStatus.NOT_FOUND.value());
            out = response.getWriter();
            out.append(errorMsg);
            out.flush();
        } catch (IOException e) {
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }

    /************************ 文件业务的操作 ******************************/

    /**
     * 根据ID信息获取文件的名称
     *
     * @param idArr
     * @return
     */
    @Override
    public Map<String, String> getNameByIds(String[] idArr) {
        LambdaQueryWrapper<SysFiles> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.in(SysFiles::getId, idArr);
        List<SysFiles> fileList = list(queryWrapper);

        Map<String, String> fileNames = new HashMap<>(7);
        for (String id : idArr) {
            String name = "未找到该文件";
            for (SysFiles sysFiles : fileList) {
                if (sysFiles.getId().equals(id)) {
                    name = sysFiles.getOriginName();
                    break;
                }
            }
            fileNames.put(id, name);
        }

        return fileNames;
    }

    @Override
    public void beforeList(UserInfo user, SysFiles entity, String keyword, Map<String, String> params, LambdaQueryWrapper<SysFiles> query) {
        query.like(SysFiles::getOriginName, keyword);
        if (StringUtils.isNotBlank(entity.getFileType())) {
            query.in(SysFiles::getFileType, AppFileUtil.getFileTypeSuffix(params.get("fileType")));
        }
        query.orderByDesc(SysFiles::getCreateTime);
    }

    @Override
    public void beforePage(UserInfo user, SysFiles entity, String keyword, IPage<SysFiles> page, Map<String, String> params, LambdaQueryWrapper<SysFiles> query) {
        if (StringUtils.isNotBlank(entity.getFileType())) {
            String[] fileTypeList = AppFileUtil.getFileTypeSuffix(entity.getFileType());
            query.in(SysFiles::getFileType, fileTypeList);
        }
        query.and(i -> i.like(SysFiles::getOriginName, keyword).or().like(SysFiles::getFileType, keyword));
        query.orderByDesc(SysFiles::getCreateTime);
    }

    @Override
    public boolean delete(String id, Map<String, String> params) {
        boolean result = false;
        SysFiles nowFile = baseMapper.selectById(id);

        // 删除图片数据
        result = super.delete(id, params);
        if (result) {
            // 删除图片文件
            AppFileUtil.deleteFile(nowFile.getFileType(), nowFile.getLocation());
        }

        return result;
    }

    @Override
    public void deleteFile(String location) {
        // 删除实际文件
        String fileType = FileUtil.getFileSuffix(location);
        AppFileUtil.deleteFile(fileType, location);

        LambdaQueryWrapper<SysFiles> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(SysFiles::getLocation, location);
        List<SysFiles> list = list(queryWrapper);
        if (list.size() > 0) {
            remove(queryWrapper);
        }
    }

    /**
     * 根据id列表，获取id对应的文件路径列表
     *
     * @param idList id列表
     * @return
     */
    @Override
    public Map<String, String> getIdToPathMap(List<String> idList) {
        // 过滤掉列表中为空的所有id
        idList = idList.stream().filter(obj -> StringUtils.isNotBlank(obj)).collect(Collectors.toList());
        if (idList.isEmpty()) {
            return new HashMap<>(7);
        }

        Map<String, String> idToPath = new HashMap<>(7);
        LambdaQueryWrapper<SysFiles> wrapperFiles = Wrappers.lambdaQuery();
        wrapperFiles.in(SysFiles::getId, idList);
        List<SysFiles> sysFiles = list(wrapperFiles);
        sysFiles.forEach(sysFile -> {
            idToPath.put(sysFile.getId(), sysFile.getLocation());
        });

        return idToPath;
    }

    /**
     * 根据系统中文件的使用情况，对未使用到的所有文件进行清理
     *
     * @return
     */
    @Override
    public List<SysFiles> clearNoUse() {
        List<SysFiles> noUseFiles = baseMapper.getNoUseFile();
        List<SysFiles> hasDelete = new ArrayList<>();

        // 删除实际文件
        List<String> delIds = new ArrayList<>();
        for (SysFiles file : noUseFiles) {
            // 删除实际文件
            AppFileUtil.deleteFile(file.getFileType(), file.getLocation());

            delIds.add(file.getId());
            hasDelete.add(file);
        }

        // 删除数据
        if (removeByIds(delIds)) {
            return hasDelete;
        } else {
            return null;
        }
    }


}
